#if defined(SWITCH)
	#include <switch.h>
#endif
#include "diablo.h"
#include "../3rdParty/Storm/Source/storm.h"
#include "../DiabloUI/diabloui.h"

DEVILUTION_BEGIN_NAMESPACE

HWND ghMainWnd;
int glMid1Seed[NUMLEVELS];
int glMid2Seed[NUMLEVELS];
int gnLevelTypeTbl[NUMLEVELS];
int MouseY;             // idb
int MouseX;             // idb
BOOL gbGameLoopStartup; // idb
int glSeedTbl[NUMLEVELS];
BOOL gbRunGame;
int glMid3Seed[NUMLEVELS];
BOOL gbRunGameResult;
int zoomflag; // weak
BOOL gbProcessPlayers;
int glEndSeed[NUMLEVELS];
BOOL gbLoadGame;
HINSTANCE ghInst; // idb
int DebugMonsters[10];
char cineflag;   // weak
int drawpanflag; // weak
int visiondebug; // weak
int scrollflag;  /* unused */
BOOL light4flag;
int leveldebug;      // weak
int monstdebug;      // weak
int trigdebug;       /* unused */
int setseed;         // weak
int debugmonsttypes; // weak
int PauseMode;       // weak
int sgnTimeoutCurs;
char sgbMouseDown;     // weak
int color_cycle_timer; // weak

/* rdata */

BOOL fullscreen = TRUE;
#ifdef _DEBUG
int showintrodebug = 1;
int questdebug = -1;
int debug_mode_key_s;
int debug_mode_key_w;
int debug_mode_key_inverted_v;
int debug_mode_dollar_sign;
int debug_mode_key_d;
int debug_mode_key_i;
int dbgplr;
int dbgqst;
int dbgmon;
int arrowdebug;
int frameflag;
int frameend;
int framerate;
int framestart;
#endif
BOOL FriendlyMode = TRUE;
char *spszMsgTbl[4] = {
	"I need help! Come Here!",
	"Follow me.",
	"Here's something for you.",
	"Now you DIE!"
};                                                      // weak
char *spszMsgKeyTbl[4] = { "F9", "F10", "F11", "F12" }; // weak

void FreeGameMem()
{
	music_stop();

	MemFreeDbg(pDungeonCels);
	MemFreeDbg(pMegaTiles);
	MemFreeDbg(pLevelPieces);
	MemFreeDbg(pSpecialCels);
	MemFreeDbg(pSpeedCels);

	FreeMissiles();
	FreeMonsters();
	FreeObjectGFX();
	FreeEffects();
	FreeTownerGFX();
}

BOOL StartGame(BOOL bNewGame, BOOL bSinglePlayer)
{
	BOOL fExitProgram;
	unsigned int uMsg;

	byte_678640 = 1;

	do {
		fExitProgram = FALSE;
		gbLoadGame = FALSE;
 	
		if (!NetInit(bSinglePlayer, &fExitProgram)) {
			gbRunGameResult = !fExitProgram;
			break;
		} 
		byte_678640 = 0;

		if (bNewGame || !gbValidSaveFile) {			 
			InitLevels();			 
			InitQuests();			 
			InitPortals();			 
			InitDungMsgs(myplr);
		}
		if (!gbValidSaveFile || !gbLoadGame)
			uMsg = WM_DIABNEWGAME;
		else
			uMsg = WM_DIABLOADGAME;
		 
		run_game_loop(uMsg);	
		NetClose();
	
		pfile_create_player_description(0, 0);
	} while (gbRunGameResult);

	SNetDestroy();
	return gbRunGameResult;
}
// 678640: using guessed type char byte_678640;

void run_game_loop(unsigned int uMsg)
{
	BOOL bLoop;
	WNDPROC saveProc;
	MSG msg;
	
	nthread_ignore_mutex(TRUE);
	start_game(uMsg);
	/// ASSERT: assert(ghMainWnd);
	saveProc = SetWindowProc(GM_Game);
#if defined(SWITCH)
	svcOutputDebugString("control_update_life_mana",20);
#endif
	control_update_life_mana();
#if defined(SWITCH)
	svcOutputDebugString("msg_process_net_packets",20);
#endif
	msg_process_net_packets();
	gbRunGame = TRUE;
	gbProcessPlayers = TRUE;
	gbRunGameResult = TRUE;
	drawpanflag = 255;
	DrawAndBlit();
	PaletteFadeIn(8);
	drawpanflag = 255;
	gbGameLoopStartup = TRUE;
	nthread_ignore_mutex(FALSE);

	while(gbRunGame) {
		diablo_color_cyc_logic();
		if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
			SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
			while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
				if(msg.message == WM_QUIT) {
					gbRunGameResult = FALSE;
					gbRunGame = FALSE;
					break;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			bLoop = gbRunGame && nthread_has_500ms_passed(FALSE);
			SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
			if(!bLoop) {
				continue;
			}
		} else if(!nthread_has_500ms_passed(FALSE)) {
#ifdef SLEEPFIX
			Sleep(1);
#endif
			continue;
		} 
 
		//svcOutputDebugString("multi_process_network_packets",20);
		multi_process_network_packets();		 
		game_loop(gbGameLoopStartup);
		//svcOutputDebugString("msgcmd_send_chat",20);
		msgcmd_send_chat();
		gbGameLoopStartup = FALSE;		 
		DrawAndBlit();
	}

	if(gbMaxPlayers > 1) {
		pfile_write_hero();
	}

	pfile_flush_W();
	PaletteFadeOut(8);
	SetCursor_(0);
	ClearScreenBuffer();
	drawpanflag = 255;
	scrollrt_draw_game_screen(TRUE);
	saveProc = SetWindowProc(saveProc);
	/// ASSERT: assert(saveProc == GM_Game);
	free_game();

	if(cineflag) {
		cineflag = FALSE;
		DoEnding();
	}
}
// 525718: using guessed type char cineflag;
// 52571C: using guessed type int drawpanflag;
// 679660: using guessed type char gbMaxPlayers;

void start_game(unsigned int uMsg)
{
	zoomflag = 1;
	cineflag = FALSE;
	InitCursor();
	InitLightTable();
	LoadDebugGFX();
	/// ASSERT: assert(ghMainWnd);
	music_stop();
	ShowProgress(uMsg);
	gmenu_init_menu();
	InitLevelCursor();
	sgnTimeoutCurs = 0;
	sgbMouseDown = 0;
	track_repeat_walk(0);
}
// 52569C: using guessed type int zoomflag;
// 525718: using guessed type char cineflag;
// 525748: using guessed type char sgbMouseDown;

void free_game()
{
	int i; // esi

	FreeControlPan();
	FreeInvGFX();
	FreeGMenu();
	FreeQuestText();
	FreeStoreMem();

	for (i = 0; i < MAX_PLRS; i++)
		FreePlayerGFX(i);

	FreeItemGFX();
	FreeCursor();
	FreeLightTable();
	FreeDebugGFX();
	FreeGameMem();
}

BOOL diablo_get_not_running()
{
	SetLastError(0);
	CreateEvent(NULL, FALSE, FALSE, "DiabloEvent");
	return GetLastError() != ERROR_ALREADY_EXISTS;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HINSTANCE hInst;
	int nData;
	char szFileName[MAX_PATH];
	BOOL bNoEvent;

	hInst = hInstance;
#ifndef DEBUGGER
	diablo_reload_process(hInstance);
#endif
	ghInst = hInst;

 
	 
	srand(GetTickCount());
	InitHash();
	fault_get_filter();

 
 #if defined(SWITCH)
		svcOutputDebugString("starting",20);
#endif
#ifdef _DEBUG
		SFileEnableDirectAccess(TRUE);
#endif
		diablo_init_screen();		 
		diablo_parse_flags(lpCmdLine);		 
		init_create_window(nCmdShow);	 
		sound_init();		 
		UiInitialize();

#ifdef _DEBUG
		if (showintrodebug)
#endif
			play_movie("gendata\\logo.smk", TRUE);

		{			
			char szValueName[] = "Intro";
			if (!SRegLoadValue("Diablo", szValueName, 0, &nData))
				nData = 1;
			if (nData)
				play_movie("gendata\\diablo1.smk", TRUE);
			SRegSaveValue("Diablo", szValueName, 0, 0);
		}

#ifdef _DEBUG
		if (showintrodebug) {
#endif
			UiTitleDialog(7);
			BlackPalette();
#ifdef _DEBUG
		}
#endif

		mainmenu_loop();
		UiDestroy();
		SaveGamma();
 
#if defined(SWITCH)
	svcOutputDebugString("ending",20);
#endif
	return FALSE;
}

void diablo_parse_flags(char *args)
{
	char c;
#ifdef _DEBUG
	int i;
#endif

	while(*args != '\0') {
		while(isspace(*args)) {
			args++;
		}
		if(_strnicmp("dd_emulate", args, strlen("dd_emulate")) == 0) {
			gbEmulate = 1;
			args += strlen("dd_emulate");
		} else if(_strnicmp("dd_backbuf", args, strlen("dd_backbuf")) == 0) {
			gbBackBuf = 1;
			args += strlen("dd_backbuf");
		} else if(_strnicmp("ds_noduplicates", args, strlen("ds_noduplicates")) == 0) {
			gbDupSounds = 0;
			args += strlen("ds_noduplicates");
		} else {
			c = tolower(*args);
			args++;
#ifdef _DEBUG
			switch(c) {
			case '^':
				debug_mode_key_inverted_v = 1;
				break;
			case '$':
				debug_mode_dollar_sign = 1;
				break;
			case 'b':
			/*
				debug_mode_key_b = 1;
			*/
				break;
			case 'd':
				showintrodebug = 0;
				debug_mode_key_d = 1;
				break;
			case 'f':
				EnableFrameCount();
				break;
			case 'i':
				debug_mode_key_i = 1;
				break;
			case 'j':
			/*
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				debug_mode_key_J_trigger = i;
			*/
				break;
			case 'l':
				setlevel = 0;
				leveldebug = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				leveltype = i;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				currlevel = i;
				plr[0].plrlevel = i;
				break;
			case 'm':
				monstdebug = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				DebugMonsters[debugmonsttypes++] = i;
				break;
			case 'n':
				showintrodebug = 0;
				break;
			case 'q':
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				questdebug = i;
				break;
			case 'r':
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				setseed = i;
				break;
			case 's':
				debug_mode_key_s = 1;
				break;
			case 't':
				leveldebug = 1;
				setlevel = 1;
				while(isspace(*args)) {
					args++;
				}
				i = 0;
				while(isdigit(*args)) {
					i = *args + 10 * i - '0';
					args++;
				}
				setlvlnum = i;
				break;
			case 'v':
				visiondebug = 1;
				break;
			case 'w':
				debug_mode_key_w = 1;
				break;
			case 'x':
				fullscreen = 0;
				break;
			}
#endif
		}
	}
}
// 52A548: using guessed type char gbBackBuf;
// 52A549: using guessed type char gbEmulate;

void diablo_init_screen()
{
	int i;

	MouseX = SCREEN_WIDTH / 2;
	MouseY = SCREEN_HEIGHT / 2;
	ScrollInfo._sdx = 0;
	ScrollInfo._sdy = 0;
	ScrollInfo._sxoff = 0;
	ScrollInfo._syoff = 0;
	ScrollInfo._sdir = SDIR_NONE;

	for (i = 0; i < 1024; i++)
		PitchTbl[i] = i * BUFFER_WIDTH;

	ClrDiabloMsg();
}

BOOL diablo_find_window(LPCSTR lpClassName)
{
#ifndef SWITCH		
	HWND result; // eax
	HWND v2;     // esi
	HWND v3;     // eax
	HWND v4;     // edi

	result = FindWindow(lpClassName, 0);
	v2 = result;
	if (!result)
		return 0;

	v3 = GetLastActivePopup(result);
	if (v3)
		v2 = v3;
	v4 = GetTopWindow(v2);
	if (!v4)
		v4 = v2;
	SetForegroundWindow(v2);
	SetFocus(v4);
#endif	
	return 1;
}

void diablo_reload_process(HINSTANCE hInstance)
{
#ifndef SWITCH	
	DWORD dwSize, dwProcessId;
	BOOL bNoExist;
	char *s;
	long *plMap;
	HWND hWnd, hPrev;
	HANDLE hMap;
	STARTUPINFO si;
	SYSTEM_INFO sinf;
	PROCESS_INFORMATION pi;
	char szReload[MAX_PATH + 16];
	char szFileName[MAX_PATH] = "";

	GetModuleFileName(hInstance, szFileName, sizeof(szFileName));
	wsprintf(szReload, "Reload-%s", szFileName);
	for(s = szReload; *s != '\0'; s++) {
		if(*s == '\\') {
			*s = '/';
		}
	}

	GetSystemInfo(&sinf);
	dwSize = sinf.dwPageSize;
	if(dwSize < 4096) {
		dwSize = 4096;
	}

	hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, SEC_COMMIT | PAGE_READWRITE, 0, dwSize, szReload);
	bNoExist = GetLastError() != ERROR_ALREADY_EXISTS;
	if(hMap == NULL) {
		return;
	}
	plMap = (long *)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, dwSize);
	if(plMap == NULL) {
		return;
	}

	if(bNoExist) {
		plMap[0] = -1;
		plMap[1] = 0;
		memset(&si, 0, sizeof(si));
		si.cb = sizeof(si);
		CreateProcess(szFileName, NULL, NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi);
		WaitForInputIdle(pi.hProcess, INFINITE);
		CloseHandle(pi.hThread);
		CloseHandle(pi.hProcess);
		while(plMap[0] < 0) {
			Sleep(1000);
		}
		UnmapViewOfFile(plMap);
		CloseHandle(hMap);
		ExitProcess(0);
	}

	if(InterlockedIncrement(plMap) == 0) {
		plMap[1] = GetCurrentProcessId();
	} else {
		hPrev = GetForegroundWindow();
		hWnd = hPrev;
		while(1) {
			hPrev = GetWindow(hPrev, GW_HWNDPREV);
			if(hPrev == NULL) {
				break;
			}
			hWnd = hPrev;
		}
		while(1) {
			GetWindowThreadProcessId(hWnd, &dwProcessId);
			if(dwProcessId == plMap[1]) {
				SetForegroundWindow(hWnd);
				break;
			}
			hWnd = GetWindow(hWnd, GW_HWNDNEXT);
			if(hWnd == NULL) {
				break;
			}
		}
		UnmapViewOfFile(plMap);
		CloseHandle(hMap);
		ExitProcess(0);
	}
#endif	
}

BOOL PressEscKey()
{
	BOOL rv = FALSE;

	if (doomflag) {
		doom_close();
		rv = TRUE;
	}
	if (helpflag) {
		helpflag = 0;
		rv = TRUE;
	}

	if (qtextflag) {
		qtextflag = 0;
		sfx_stop();
		rv = TRUE;
	} else if (stextflag) {
		STextESC();
		rv = TRUE;
	}

	if (msgflag) {
		msgdelay = 0;
		rv = TRUE;
	}
	if (talkflag) {
		control_reset_talk();
		rv = TRUE;
	}
	if (dropGoldFlag) {
		control_drop_gold(VK_ESCAPE);
		rv = TRUE;
	}
	if (spselflag) {
		spselflag = 0;
		rv = TRUE;
	}

	return rv;
}
// 4B8960: using guessed type int talkflag;
// 4B8C98: using guessed type int spselflag;
// 52575C: using guessed type int doomflag;
// 52B9F0: using guessed type char msgdelay;
// 52B9F1: using guessed type char msgflag;
// 646D00: using guessed type char qtextflag;
// 6AA705: using guessed type char stextflag;

LRESULT CALLBACK DisableInputWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_CHAR:
	case WM_SYSKEYDOWN:
	case WM_SYSCOMMAND:
	case WM_MOUSEMOVE:
		return 0;
	case WM_LBUTTONDOWN:
		if (sgbMouseDown == 0) {
			sgbMouseDown = 1;
			SetCapture(hWnd);
		}
		return 0;
	case WM_LBUTTONUP:
		if (sgbMouseDown == 1) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_RBUTTONDOWN:
		if (sgbMouseDown == 0) {
			sgbMouseDown = 2;
			SetCapture(hWnd);
		}
		return 0;
	case WM_RBUTTONUP:
		if (sgbMouseDown == 2) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_CAPTURECHANGED:
		if (hWnd != (HWND)lParam)
			sgbMouseDown = 0;
		return 0;
	}

	return MainWndProc(hWnd, uMsg, wParam, lParam);
}
// 525748: using guessed type char sgbMouseDown;

LRESULT CALLBACK GM_Game(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_KEYDOWN:
		PressKey(wParam);
		return 0;
	case WM_KEYUP:
		ReleaseKey(wParam);
		return 0;
	case WM_CHAR:
		PressChar(wParam);
		return 0;
	case WM_SYSKEYDOWN:
		if (PressSysKey(wParam))
			return 0;
		break;
	case WM_SYSCOMMAND:
		if (wParam == SC_CLOSE) {
			gbRunGame = FALSE;
			gbRunGameResult = FALSE;
			return 0;
		}
		break;
	case WM_MOUSEMOVE:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		gmenu_on_mouse_move();
		return 0;
	case WM_LBUTTONDOWN:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if (sgbMouseDown == 0) {
			sgbMouseDown = 1;
			SetCapture(hWnd);
			track_repeat_walk(LeftMouseDown(wParam));
		}
		return 0;
	case WM_LBUTTONUP:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if (sgbMouseDown == 1) {
			sgbMouseDown = 0;
			LeftMouseUp();
			track_repeat_walk(FALSE);
			ReleaseCapture();
		}
		return 0;
	case WM_RBUTTONDOWN:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if (sgbMouseDown == 0) {
			sgbMouseDown = 2;
			SetCapture(hWnd);
			RightMouseDown();
		}
		return 0;
	case WM_RBUTTONUP:
		MouseX = LOWORD(lParam);
		MouseY = HIWORD(lParam);
		if (sgbMouseDown == 2) {
			sgbMouseDown = 0;
			ReleaseCapture();
		}
		return 0;
	case WM_CAPTURECHANGED:
		if (hWnd != (HWND)lParam) {
			sgbMouseDown = 0;
			track_repeat_walk(FALSE);
		}
		break;
	case WM_DIABNEXTLVL:
	case WM_DIABPREVLVL:
	case WM_DIABRTNLVL:
	case WM_DIABSETLVL:
	case WM_DIABWARPLVL:
	case WM_DIABTOWNWARP:
	case WM_DIABTWARPUP:
	case WM_DIABRETOWN:
		if (gbMaxPlayers > 1)
			pfile_write_hero();
		nthread_ignore_mutex(TRUE); 	
		PaletteFadeOut(8);
		FreeMonsterSnd();
		music_stop();
		track_repeat_walk(FALSE);
		sgbMouseDown = 0;
		ReleaseCapture();
		ShowProgress(uMsg);
		drawpanflag = 255;
		DrawAndBlit();
		if (gbRunGame)
			PaletteFadeIn(8);
		nthread_ignore_mutex(FALSE);
		gbGameLoopStartup = TRUE;
		return 0;
	}

	return MainWndProc(hWnd, uMsg, wParam, lParam);
}
// 52571C: using guessed type int drawpanflag;
// 525748: using guessed type char sgbMouseDown;
// 679660: using guessed type char gbMaxPlayers;

BOOL LeftMouseDown(int wParam)
{
	if (!gmenu_left_mouse(TRUE) && !control_check_talk_btn() && !sgnTimeoutCurs) {
		if (deathflag) {
			control_check_btn_press();
		} else if (PauseMode != 2) {
			if (doomflag) {
				doom_close();
			} else if (spselflag) {
				SetSpell();
			} else if (stextflag) {
				CheckStoreBtn();
			} else if (MouseY < VIEWPORT_HEIGHT) {
				if (!gmenu_exception() && !TryIconCurs()) {
					if (questlog && MouseX > 32 && MouseX < 288 && MouseY > 32 && MouseY < 308) {
						QuestlogESC();
					} else if (qtextflag) {
						qtextflag = FALSE;
						sfx_stop();
					} else if (chrflag && MouseX < 320) {
						CheckChrBtns();
					} else if (invflag && MouseX > 320) {
						if (!dropGoldFlag)
							CheckInvItem();
					} else if (sbookflag && MouseX > 320) {
						CheckSBook();
					} else if (pcurs >= CURSOR_FIRSTITEM) {
						if (TryInvPut()) {
							NetSendCmdPItem(TRUE, CMD_PUTITEM, MouseX, MouseY);
							SetCursor_(CURSOR_HAND);
						}
					} else {
						if (plr[myplr]._pStatPts && !spselflag)
							CheckLvlBtn();
						if (!lvlbtndown)
							return LeftMouseCmd(wParam == MK_SHIFT + MK_LBUTTON);
					}
				}
			} else {
				if (!talkflag && !dropGoldFlag && !gmenu_exception())
						CheckInvScrn();
				DoPanBtn();
				if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM)
					SetCursor_(CURSOR_HAND);
			}
		}
	}

	return FALSE;
}

BOOL LeftMouseCmd(BOOL bShift)
{
	char debug[256];
	BOOL bNear;

	if (!leveltype) {                                // in town
		if (pcursitem != -1 && pcurs <= CURSOR_HAND) // JAKE: allow no cursor as well
			NetSendCmdLocParam1(TRUE, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
		if (pcursmonst != -1)
			NetSendCmdLocParam1(TRUE, CMD_TALKXY, cursmx, cursmy, pcursmonst);
		if (pcursitem == -1 && pcursmonst == -1 && pcursplr == -1)
			return TRUE;
	} else { // in dungeon
		bNear = abs(plr[myplr].WorldX - cursmx) < 2 && abs(plr[myplr].WorldY - cursmy) < 2;
#if defined(SWITCH)
		sprintf(debug, " bNear = %d, pcurs = %d, object[pcursobj]._oBreak = %d, pcursitem = %d", bNear, pcurs, object[pcursobj]._oBreak, pcursitem);
		svcOutputDebugString(debug, 256);
#endif
		if (pcursitem != -1 && pcurs <= CURSOR_HAND && !bShift) { // JAKE: allow no cursor as well
			cursmx = 320;
			cursmy = 240;
			NetSendCmdLocParam1(pcurs, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
		} else if (pcursobj != -1 && (!bShift || bNear && object[pcursobj]._oBreak == 1)) {
			cursmx = 320;
			cursmy = 240;
			NetSendCmdLocParam1(TRUE, pcurs == CURSOR_DISARM ? CMD_DISARMXY : CMD_OPOBJXY, cursmx, cursmy, pcursobj);
		} else if (plr[myplr]._pwtype == WT_RANGED) {
			if (bShift) {
				NetSendCmdLoc(TRUE, CMD_RATTACKXY, cursmx, cursmy);
			} else if (pcursmonst != -1) {
				if (CanTalkToMonst(pcursmonst)) {
					NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
				} else {
					NetSendCmdParam1(TRUE, CMD_RATTACKID, pcursmonst);
				}
			} else if (pcursplr != -1 && !FriendlyMode) {
				NetSendCmdParam1(TRUE, CMD_RATTACKPID, pcursplr);
			}
		} else {
			if (bShift) {
				if (pcursmonst != -1) {
					if (CanTalkToMonst(pcursmonst)) {
						NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
					} else {
						NetSendCmdLoc(TRUE, CMD_SATTACKXY, cursmx, cursmy);
					}
				} else {
					NetSendCmdLoc(TRUE, CMD_SATTACKXY, cursmx, cursmy);
				}
			} else if (pcursmonst != -1) {
				NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
			} else if (pcursplr != -1 && !FriendlyMode) {
				NetSendCmdParam1(TRUE, CMD_ATTACKPID, pcursplr);
			}
		}
		if (!bShift && pcursitem == -1 && pcursobj == -1 && pcursmonst == -1 && pcursplr == -1)
			return TRUE;
	}

	return FALSE;
}

BOOL TryIconCurs()
{
	if(pcurs == CURSOR_RESURRECT) {
		NetSendCmdParam1(TRUE, CMD_RESURRECT, pcursplr);
		return TRUE;
	} else if(pcurs == CURSOR_HEALOTHER) {
		NetSendCmdParam1(TRUE, CMD_HEALOTHER, pcursplr);
		return TRUE;
	} else if(pcurs == CURSOR_TELEKINESIS) {
		DoTelekinesis();
		return TRUE;
	} else if(pcurs == CURSOR_IDENTIFY) {
		if(pcursinvitem != -1) {
			CheckIdentify(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_REPAIR) {
		if(pcursinvitem != -1) {
			DoRepair(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_RECHARGE) {
		if(pcursinvitem != -1) {
			DoRecharge(myplr, pcursinvitem);
		} else {
			SetCursor_(CURSOR_HAND);
		}
		return TRUE;
	} else if(pcurs == CURSOR_TELEPORT) {
		if(pcursmonst != -1) {
			NetSendCmdParam3(TRUE, CMD_TSPELLID, pcursmonst, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		} else if(pcursplr != -1) {
			NetSendCmdParam3(TRUE, CMD_TSPELLPID, pcursplr, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		} else {
			NetSendCmdLocParam2(TRUE, CMD_TSPELLXY, cursmx, cursmy, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
		}
		SetCursor_(CURSOR_HAND);
		return TRUE;
	} else if(pcurs == CURSOR_DISARM && pcursobj == -1) {
		SetCursor_(CURSOR_HAND);
		return TRUE;
	}

	return FALSE;
}
// 4B8CB8: using guessed type char pcursinvitem;
// 4B8CC1: using guessed type char pcursobj;
// 4B8CC2: using guessed type char pcursplr;

void LeftMouseUp()
{
	gmenu_left_mouse(FALSE);
	control_release_talk_btn();
	if (panbtndown)
		CheckBtnUp();
	if (chrbtnactive)
		ReleaseChrBtns();
	if (lvlbtndown)
		ReleaseLvlBtn();
	if (stextflag)
		ReleaseStoreBtn();
}
// 4B851C: using guessed type int lvlbtndown;
// 6AA705: using guessed type char stextflag;

void RightMouseDown()
{
	if (!gmenu_exception() && sgnTimeoutCurs == CURSOR_NONE && PauseMode != 2 && !plr[myplr]._pInvincible) {
		if (doomflag) {
			doom_close();
		} else if (!stextflag) {
			if (spselflag) {
				SetSpell();
			} else if (MouseY >= VIEWPORT_HEIGHT
			    || (!sbookflag || MouseX <= 320)
			        && !TryIconCurs()
			        && (pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem))) {
				if (pcurs <= 1) { // JAKE: Allow people without cursor to cast spells too
					if (pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem))
						CheckPlrSpell();
				} else if (pcurs > 1 && pcurs < 12) {
					SetCursor_(CURSOR_HAND);
				}
			}
		}
	}
}
// 4B8968: using guessed type int sbookflag;
// 4B8C98: using guessed type int spselflag;
// 4B8CB8: using guessed type char pcursinvitem;
// 525740: using guessed type int PauseMode;
// 52575C: using guessed type int doomflag;
// 6AA705: using guessed type char stextflag;

BOOL PressSysKey(int wParam)
{
	if (gmenu_exception() || wParam != VK_F10)
		return FALSE;
	diablo_hotkey_msg(1);
	return TRUE;
}

void diablo_hotkey_msg(DWORD dwMsg)
{
	char *s;
	char szFileName[MAX_PATH];
	char szMsg[MAX_SEND_STR_LEN];

	if(gbMaxPlayers == 1) {
		return;
	}
	if(GetModuleFileName(ghInst, szFileName, sizeof(szFileName)) == 0) {
		app_fatal("Can't get program name");
	}

	s = strrchr(szFileName, '\\');
	if(s != NULL) {
		*s = '\0';
	}

	strcat(szFileName, "\\Diablo.ini");
	/// ASSERT: assert(dwMsg < sizeof(spszMsgTbl) / sizeof(spszMsgTbl[0]));
	GetPrivateProfileString("NetMsg", spszMsgKeyTbl[dwMsg], spszMsgTbl[dwMsg], szMsg, sizeof(szMsg), szFileName);
	NetSendCmdString(-1, szMsg);
}
// 48436C: using guessed type char *spszMsgTbl[4];
// 48437C: using guessed type char *spszMsgKeyTbl[4];
// 679660: using guessed type char gbMaxPlayers;

void ReleaseKey(int vkey)
{
	if (vkey == VK_SNAPSHOT)
		CaptureScreen();
}

static DWORD menuopenslow;
extern DWORD ticks;

void PressKey(int vkey)
{
	if(gmenu_presskeys(vkey) || control_presskeys(vkey)) {
		return;
	}

	ticks = GetTickCount();

	if(deathflag) {
		if(sgnTimeoutCurs != 0) {
			return;
		}
		if(vkey == VK_F9) {
			diablo_hotkey_msg(0);
		}
		if(vkey == VK_F10) {
			diablo_hotkey_msg(1);
		}
		if(vkey == VK_F11) {
			diablo_hotkey_msg(2);
		}
		if(vkey == VK_F12) {
			diablo_hotkey_msg(3);
		}
		if(vkey == VK_RETURN) {
			control_type_message();
		}
		if(vkey != VK_ESCAPE) {
			return;
		}
	}
	if(vkey == VK_ESCAPE) {
		if(!PressEscKey()) {
			track_repeat_walk(0);
			gamemenu_previous();
		}
		return;
	}

	if(sgnTimeoutCurs != 0 || dropGoldFlag) {
		return;
	}
	if(vkey == VK_PAUSE) {
		diablo_pause_game();
		return;
	}
	if(PauseMode == 2) {
		return;
	}

	if(vkey == VK_RETURN) {
		if(stextflag) {
			STextEnter();
		} else if(questlog) {
			QuestlogEnter();
		} else {
			control_type_message();
		}
	} else if(vkey == VK_F1) {
		if(helpflag) {
			helpflag = 0;
		} else if(stextflag) {
			ClearPanel();
			AddPanelString("No help available", TRUE); /// BUGFIX: message isn't displayed
			AddPanelString("while in stores", TRUE);
			track_repeat_walk(0);
		} else {
			invflag = 0;
			chrflag = 0;
			sbookflag = 0;
			spselflag = 0;
			if(qtextflag && leveltype == DTYPE_TOWN) {
				qtextflag = 0;
				sfx_stop();
			}
			questlog = FALSE;
			automapflag = 0;
			msgdelay = 0;
			gamemenu_off();
			DisplayHelp();
			doom_close();
		}
	}
#ifdef _DEBUG
	else if(vkey == VK_F2) {
	}
#endif
#ifdef _DEBUG
	else if(vkey == VK_F3) {
		if(pcursitem != -1) {
			sprintf(
				tempstr,
				"IDX = %i  :  Seed = %i  :  CF = %i",
				item[pcursitem].IDidx,
				item[pcursitem]._iSeed,
				item[pcursitem]._iCreateInfo);
			NetSendCmdString(1 << myplr, tempstr);
		}
		sprintf(tempstr, "Numitems : %i", numitems);
		NetSendCmdString(1 << myplr, tempstr);
	}
#endif
#ifdef _DEBUG
	else if(vkey == VK_F4) {
		PrintDebugQuest();
	}
#endif
	else if(vkey == VK_F5) {
		if(spselflag) {
			SetSpeedSpell(0);
		} else {
			ToggleSpell(0);
		}
	} else if(vkey == VK_F6) {
		if(spselflag) {
			SetSpeedSpell(1);
		} else {
			ToggleSpell(1);
		}
	} else if(vkey == VK_F7) {
		if(spselflag) {
			SetSpeedSpell(2);
		} else {
			ToggleSpell(2);
		}
	} else if(vkey == VK_F8) {
		if(spselflag) {
			SetSpeedSpell(3);
		} else {
			ToggleSpell(3);
		}
	} else if(vkey == VK_F9) {
		diablo_hotkey_msg(0);
	} else if(vkey == VK_F10) {
		diablo_hotkey_msg(1);
	} else if(vkey == VK_F11) {
		diablo_hotkey_msg(2);
	} else if(vkey == VK_F12) {
		diablo_hotkey_msg(3);
	} else if(vkey == VK_UP) {
		if(stextflag) {
			STextUp();
		} else if(questlog) {
			QuestlogUp();
		} else if(helpflag) {
			HelpScrollUp();
		} else if(automapflag) {
			if (GetAsyncKeyState(VK_SHIFT) & 0x8000) // JAKE: [1] no move when u move
				AutomapUp();
		}
	} else if(vkey == VK_DOWN) {
		if(stextflag) {
			STextDown();
		} else if(questlog) {
			QuestlogDown();
		} else if(helpflag) {
			HelpScrollDown();
		} else if(automapflag) {
			if (GetAsyncKeyState(VK_SHIFT) & 0x8000) // JAKE: [2] no move when u move
				AutomapDown();
		}
	} else if(vkey == VK_PRIOR) {
		if(stextflag) {
			STextPrior();
		}
	} else if(vkey == VK_NEXT) {
		if(stextflag) {
			STextNext();
		}
	} else if(vkey == VK_LEFT) {
		if(automapflag && !talkflag) {
			if (GetAsyncKeyState(VK_SHIFT) & 0x8000) // JAKE: [3] no move when u move
				AutomapLeft();
		}
	} else if(vkey == VK_RIGHT) {
		if(automapflag && !talkflag) {
			if (GetAsyncKeyState(VK_SHIFT) & 0x8000) // JAKE: [4] no move when u move
				AutomapRight();
		}
	} else if(vkey == VK_TAB) {
		DoAutoMap();
	} else if(vkey == VK_SPACE) {
		//if (ticks - menuopenslow < 300) {
		//	return;
		//}
		//menuopenslow = ticks;
		if (stextflag) {
			STextEnter();
		} else if (questlog) {
			QuestlogEnter();
		} else {
			control_type_message();
		}
		/*if(!chrflag && invflag && MouseX < 480 && MouseY < VIEWPORT_HEIGHT) {
			SetCursorPos(MouseX + 160, MouseY);
		}
		if(!invflag && chrflag && MouseX > 160 && MouseY < VIEWPORT_HEIGHT) {
			SetCursorPos(MouseX - 160, MouseY);
		}
		helpflag = 0;
		invflag = 0;
		chrflag = 0;
		sbookflag = 0;
		spselflag = 0;
		if(qtextflag && leveltype == DTYPE_TOWN) {
			qtextflag = 0;
			sfx_stop();
		}
		if (stextflag) {
			STextEnter();
		}
		questlog = FALSE;
		automapflag = 0;
		msgdelay = 0;
		gamemenu_off();
		doom_close();*/
	}
}
// 4B8960: using guessed type int talkflag;
// 4B8968: using guessed type int sbookflag;
// 4B8C98: using guessed type int spselflag;
// 525740: using guessed type int PauseMode;
// 52B9F0: using guessed type char msgdelay;
// 646D00: using guessed type char qtextflag;
// 6AA705: using guessed type char stextflag;

void diablo_pause_game()
{
	if ((unsigned char)gbMaxPlayers <= 1u) {
		if (PauseMode) {
			PauseMode = 0;
		} else {
			PauseMode = 2;
			FreeMonsterSnd();
			track_repeat_walk(0);
		}
		drawpanflag = 255;
	}
}
// 52571C: using guessed type int drawpanflag;
// 525740: using guessed type int PauseMode;
// 679660: using guessed type char gbMaxPlayers;

/* NOTE: `return` must be used instead of `break` to be bin exact as C++ */
void PressChar(int vkey)
{
	if(gmenu_exception() || control_talk_last_key(vkey) || sgnTimeoutCurs != 0 || deathflag) {
		return;
	}
	if((char)vkey == 'p' || (char)vkey == 'P') {
		diablo_pause_game();
		return;
	}
	if(PauseMode == 2) {
		return;
	}
	if(doomflag) {
		doom_close();
		return;
	}
	if(dropGoldFlag) {
		control_drop_gold(vkey);
		return;
	}

	switch(vkey) {
	case 'G':
	case 'g':
		DecreaseGamma();
		return;
	case 'F':
	case 'f':
		IncreaseGamma();
		return;
	case 'I':
	case 'i':
		if(!stextflag) {
			sbookflag = 0;
			invflag = invflag == 0;
			//if(!invflag || chrflag) {
				/*if(MouseX < 480 && MouseY < VIEWPORT_HEIGHT) {
					SetCursorPos(MouseX + 160, MouseY);
				}*/
				// JAKE: Show cursor if inventory window open, set cursor to inv slot 1
				if (newCurHidden) {
					SetCursor_(CURSOR_HAND);
					newCurHidden = false;
				}
				SetCursorPos((InvRect[25].X + (INV_SLOT_SIZE_PX / 2)), (InvRect[25].Y - (INV_SLOT_SIZE_PX / 2))); // inv cells are 29x29
			    MouseX = (InvRect[25].X + (INV_SLOT_SIZE_PX / 2));
			    MouseY = (InvRect[25].Y - (INV_SLOT_SIZE_PX / 2));
				return;                                                                                           // don't do the other cursor move stuff
				//
			/*} else {
				if(MouseX > 160 && MouseY < VIEWPORT_HEIGHT) {
					SetCursorPos(MouseX - 160, MouseY);
				}
			}*/
		}
		return;
	case 'C':
	case 'c':
		if(!stextflag) {
			questlog = FALSE;
			chrflag = chrflag == 0;

			if (newCurHidden) {
				SetCursor_(CURSOR_HAND);
				newCurHidden = false;
			}

			if(!chrflag || invflag) {
				if(MouseX > 160 && MouseY < VIEWPORT_HEIGHT) {
					SetCursorPos(MouseX - 160, MouseY);
					MouseX = MouseX - 160;
					MouseY = MouseY;
				}
			} else {
				if (!chrbtnactive && plr[myplr]._pStatPts) {
					int x = attribute_inc_rects2[0][0] + (attribute_inc_rects2[0][2] / 2);
					int y = attribute_inc_rects2[0][1] + (attribute_inc_rects2[0][3] / 2);
					SetCursorPos(x, y);
					MouseX = x;
					MouseY = y;
				} else {
					if(MouseX < 480 && MouseY < VIEWPORT_HEIGHT) {
						SetCursorPos(MouseX + 160, MouseY);
						MouseX = MouseX + 160;
						MouseY = MouseY;
					}
				}
			}
		}
		return;
	case 'Q':
	case 'q':
		if(!stextflag) {
			chrflag = 0;
			if(!questlog) {
				StartQuestlog();
			} else {
				questlog = FALSE;
			}
		}
		return;
	case 'Z':
	case 'z':
		zoomflag = zoomflag == 0;
		return;
	case 'H': // JAKE: Changed, used to be 'S' and 's'
	case 'h':
		//SetAllSpellsCheat(); // for debugging
		if (!stextflag) {
			invflag = 0;
			if (spselflag)
				spselflag = 0;
			else
				DoSpeedBook();
			track_repeat_walk(0);
		}
		return;
	case 'x':
	case 'X':
		// JAKE: Spacebar used to go back, now Z goes back.
		if (pcurs >= CURSOR_FIRSTITEM && invflag)
			DropItemBeforeTrig();
		//castwait = ticks;
		if (!invflag && !talkflag)
			RightMouseDown();
		PressEscKey();
		return;
	case 'B':
	case 'b':
		if(!stextflag) {
			invflag = 0;
			sbookflag = sbookflag == 0;
		}
		return;
	case '+':
	case '=':
		if(automapflag) {
			AutomapZoomIn();
		}
		return;
	case '-':
	case '_':
		if(automapflag) {
			AutomapZoomOut();
		}
		return;
	case 'v':
		NetSendCmdString(1 << myplr, gszProductName);
		return;
	case 'V':
		NetSendCmdString(1 << myplr, gszVersionNumber);
		return;
	case '!':
	case '1':
		if(plr[myplr].SpdList[0]._itype != -1 && plr[myplr].SpdList[0]._itype != 11) {
			UseInvItem(myplr, 47);
		}
		return;
	case '@':
	case '2':
		if(plr[myplr].SpdList[1]._itype != -1 && plr[myplr].SpdList[1]._itype != 11) {
			UseInvItem(myplr, 48);
		}
		return;
	case '#':
	case '3':
		if(plr[myplr].SpdList[2]._itype != -1 && plr[myplr].SpdList[2]._itype != 11) {
			UseInvItem(myplr, 49);
		}
		return;
	case '$':
	case '4':
		if(plr[myplr].SpdList[3]._itype != -1 && plr[myplr].SpdList[3]._itype != 11) {
			UseInvItem(myplr, 50);
		}
		return;
	case '%':
	case '5':
		if(plr[myplr].SpdList[4]._itype != -1 && plr[myplr].SpdList[4]._itype != 11) {
			UseInvItem(myplr, 51);
		}
		return;
	case '^':
	case '6':
		if(plr[myplr].SpdList[5]._itype != -1 && plr[myplr].SpdList[5]._itype != 11) {
			UseInvItem(myplr, 52);
		}
		return;
	case '&':
	case '7':
		if(plr[myplr].SpdList[6]._itype != -1 && plr[myplr].SpdList[6]._itype != 11) {
			UseInvItem(myplr, 53);
		}
		return;
	case '*':
	case '8':
#ifdef _DEBUG
		if(debug_mode_key_inverted_v || debug_mode_key_w) {
			NetSendCmd(TRUE, CMD_CHEAT_EXPERIENCE);
			return;
		}
#endif
		if(plr[myplr].SpdList[7]._itype != -1 && plr[myplr].SpdList[7]._itype != 11) {
			UseInvItem(myplr, 54);
		}
		return;
#ifdef _DEBUG
	case ')':
	case '0':
		if(debug_mode_key_inverted_v) {
			if(arrowdebug > 2) {
				arrowdebug = 0;
			}
			if(arrowdebug == 0) {
				plr[myplr]._pIFlags &= ~ISPL_FIRE_ARROWS;
				plr[myplr]._pIFlags &= ~ISPL_LIGHT_ARROWS;
			}
			if(arrowdebug == 1) {
				plr[myplr]._pIFlags |= ISPL_FIRE_ARROWS;
			}
			if(arrowdebug == 2) {
				plr[myplr]._pIFlags |= ISPL_LIGHT_ARROWS;
			}
			arrowdebug++;
		}
		return;
	case ':':
		if(currlevel == 0 && debug_mode_key_w) {
			SetAllSpellsCheat();
		}
		return;
	case '[':
		if(currlevel == 0 && debug_mode_key_w) {
			TakeGoldCheat();
		}
		return;
	case ']':
		if(currlevel == 0 && debug_mode_key_w) {
			MaxSpellsCheat();
		}
		return;
	case 'a':
		if(debug_mode_key_inverted_v) {
			spelldata[SPL_TELEPORT].sTownSpell = 1;
			plr[myplr]._pSplLvl[plr[myplr]._pSpell]++;
		}
		return;
	case 'D':
		PrintDebugPlayer(TRUE);
		return;
	case 'd':
		PrintDebugPlayer(FALSE);
		return;
	case 'e':
		if(debug_mode_key_d) {
			sprintf(tempstr, "EFlag = %i", plr[myplr]._peflag);
			NetSendCmdString(1 << myplr, tempstr);
		}
		return;
	case 'L':
	case 'l':
		if(debug_mode_key_inverted_v) {
			ToggleLighting();
		}
		return;
	case 'M':
		NextDebugMonster();
		return;
	case 'm':
		GetDebugMonster();
		return;
	case 'R':
	case 'r':
		sprintf(tempstr, "seed = %i", glSeedTbl[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		sprintf(tempstr, "Mid1 = %i : Mid2 = %i : Mid3 = %i", glMid1Seed[currlevel], glMid2Seed[currlevel], glMid3Seed[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		sprintf(tempstr, "End = %i", glEndSeed[currlevel]);
		NetSendCmdString(1 << myplr, tempstr);
		return;
	case 'T':
	case 't':
		if(debug_mode_key_inverted_v) {
			sprintf(tempstr, "PX = %i  PY = %i", plr[myplr].WorldX, plr[myplr].WorldY);
			NetSendCmdString(1 << myplr, tempstr);
			sprintf(tempstr, "CX = %i  CY = %i  DP = %i", cursmx, cursmy, dungeon[cursmx][cursmy]);
			NetSendCmdString(1 << myplr, tempstr);
		}
		return;
	case '|':
		if(currlevel == 0 && debug_mode_key_w) {
			GiveGoldCheat();
		}
		return;
	case '~':
		if(currlevel == 0 && debug_mode_key_w) {
			StoresCheat();
		}
		return;
#endif
	}
}
// 4B8968: using guessed type int sbookflag;
// 4B8C98: using guessed type int spselflag;
// 52569C: using guessed type int zoomflag;
// 525740: using guessed type int PauseMode;
// 52575C: using guessed type int doomflag;
// 6AA705: using guessed type char stextflag;

void LoadLvlGFX()
{
	/// ASSERT: assert(! pDungeonCels);

	switch((unsigned char)leveltype) {
	case DTYPE_TOWN:
		pDungeonCels = LoadFileInMem("Levels\\TownData\\Town.CEL", 0);
		pMegaTiles = LoadFileInMem("Levels\\TownData\\Town.TIL", 0);
		pLevelPieces = LoadFileInMem("Levels\\TownData\\Town.MIN", 0);
		pSpecialCels = LoadFileInMem("Levels\\TownData\\TownS.CEL", 0);
		break;
	case DTYPE_CATHEDRAL:
		pDungeonCels = LoadFileInMem("Levels\\L1Data\\L1.CEL", 0);
		pMegaTiles = LoadFileInMem("Levels\\L1Data\\L1.TIL", 0);
		pLevelPieces = LoadFileInMem("Levels\\L1Data\\L1.MIN", 0);
		pSpecialCels = LoadFileInMem("Levels\\L1Data\\L1S.CEL", 0);
		break;
	case DTYPE_CATACOMBS:
		pDungeonCels = LoadFileInMem("Levels\\L2Data\\L2.CEL", 0);
		pMegaTiles = LoadFileInMem("Levels\\L2Data\\L2.TIL", 0);
		pLevelPieces = LoadFileInMem("Levels\\L2Data\\L2.MIN", 0);
		pSpecialCels = LoadFileInMem("Levels\\L2Data\\L2S.CEL", 0);
		break;
	case DTYPE_CAVES:
		pDungeonCels = LoadFileInMem("Levels\\L3Data\\L3.CEL", 0);
		pMegaTiles = LoadFileInMem("Levels\\L3Data\\L3.TIL", 0);
		pLevelPieces = LoadFileInMem("Levels\\L3Data\\L3.MIN", 0);
		pSpecialCels = LoadFileInMem("Levels\\L1Data\\L1S.CEL", 0);
		break;
	case DTYPE_HELL:
		pDungeonCels = LoadFileInMem("Levels\\L4Data\\L4.CEL", 0);
		pMegaTiles = LoadFileInMem("Levels\\L4Data\\L4.TIL", 0);
		pLevelPieces = LoadFileInMem("Levels\\L4Data\\L4.MIN", 0);
		pSpecialCels = LoadFileInMem("Levels\\L2Data\\L2S.CEL", 0);
		break;
	default:
		app_fatal("LoadLvlGFX");
		break;
	}
}

void LoadAllGFX()
{
	/// ASSERT: assert(! pSpeedCels);
	pSpeedCels = DiabloAllocPtr(0x100000);
	IncProgress();
	IncProgress();
	InitObjectGFX();
	IncProgress();
	InitMissileGFX();
	IncProgress();
}

void CreateLevel(int lvldir)
{
	switch(leveltype) {
	case DTYPE_TOWN:
		CreateTown(lvldir);
		InitTownTriggers();
		LoadRndLvlPal(0);
		break;
	case DTYPE_CATHEDRAL:
		CreateL5Dungeon(glSeedTbl[currlevel], lvldir);
		InitL1Triggers();
		Freeupstairs();
		LoadRndLvlPal(1);
		break;
	case DTYPE_CATACOMBS:
		CreateL2Dungeon(glSeedTbl[currlevel], lvldir);
		InitL2Triggers();
		Freeupstairs();
		LoadRndLvlPal(2);
		break;
	case DTYPE_CAVES:
		CreateL3Dungeon(glSeedTbl[currlevel], lvldir);
		InitL3Triggers();
		Freeupstairs();
		LoadRndLvlPal(3);
		break;
	case DTYPE_HELL:
		CreateL4Dungeon(glSeedTbl[currlevel], lvldir);
		InitL4Triggers();
		Freeupstairs();
		LoadRndLvlPal(4);
		break;
	default:
		app_fatal("CreateLevel");
		break;
	}
}

void LoadGameLevel(BOOL firstflag, int lvldir)
{
	int i, j;
	BOOL visited;

	if (setseed)
		glSeedTbl[currlevel] = setseed;

	music_stop();
	//SetCursor_(CURSOR_HAND);
	SetRndSeed(glSeedTbl[currlevel]);
	IncProgress();
	MakeLightTable();
	LoadLvlGFX();
	IncProgress();

	if (firstflag) {
		InitInv();
		InitItemGFX();
		InitQuestText();

		for (i = 0; i < gbMaxPlayers; i++)
			InitPlrGFXMem(i);

		InitStores();
		InitAutomapOnce();
		InitHelp();
	}

	SetRndSeed(glSeedTbl[currlevel]);

	if (leveltype == DTYPE_TOWN)
		SetupTownStores();

	IncProgress();
	InitAutomap();

	if (leveltype != DTYPE_TOWN && lvldir != 4) {
		InitLighting();
		InitVision();
	}

	InitLevelMonsters();
	IncProgress();

	if (!setlevel) {
		CreateLevel(lvldir);
		IncProgress();
		FillSolidBlockTbls();
		SetRndSeed(glSeedTbl[currlevel]);

		if (leveltype != DTYPE_TOWN) {
			GetLevelMTypes();
			InitThemes();
			LoadAllGFX();
		} else {
			InitMissileGFX();
		}

		IncProgress();

		if (lvldir == 3)
			GetReturnLvlPos();
		if (lvldir == 5)
			GetPortalLvlPos();

		IncProgress();

		for (i = 0; i < MAX_PLRS; i++) {
			if (plr[i].plractive && currlevel == plr[i].plrlevel) {
				InitPlayerGFX(i);
				if (lvldir != 4)
					InitPlayer(i, firstflag);
			}
		}

		PlayDungMsgs();
		InitMultiView();
		IncProgress();

		visited = FALSE;
		for (i = 0; i < gbMaxPlayers; i++) {
			if (plr[i].plractive)
				visited = visited || plr[i]._pLvlVisited[currlevel];
		}

		SetRndSeed(glSeedTbl[currlevel]);

		if (leveltype != DTYPE_TOWN) {
			if (firstflag || lvldir == 4 || !plr[myplr]._pLvlVisited[currlevel] || gbMaxPlayers != 1) {
				HoldThemeRooms();
				glMid1Seed[currlevel] = GetRndSeed();
				InitMonsters();
				glMid2Seed[currlevel] = GetRndSeed();
				InitObjects();
				InitItems();
				CreateThemeRooms();
				glMid3Seed[currlevel] = GetRndSeed();
				InitMissiles();
				InitDead();
				glEndSeed[currlevel] = GetRndSeed();

				if (gbMaxPlayers != 1)
					DeltaLoadLevel();

				IncProgress();
				SavePreLighting();
			} else {
				InitMonsters();
				InitMissiles();
				InitDead();
				IncProgress();
				LoadLevel();
				IncProgress();
			}
		} else {
			for (i = 0; i < MAXDUNX; i++) {
				for (j = 0; j < MAXDUNY; j++)
					dFlags[i][j] |= BFLAG_LIT;
			}

			InitTowners();
			InitItems();
			InitMissiles();
			IncProgress();

			if (!firstflag && lvldir != 4 && plr[myplr]._pLvlVisited[currlevel] && gbMaxPlayers == 1)
				LoadLevel();
			if (gbMaxPlayers != 1)
				DeltaLoadLevel();

			IncProgress();
		}
		if (gbMaxPlayers == 1)
			ResyncQuests();
		else
			ResyncMPQuests();
	} else {
		/// ASSERT: assert(! pSpeedCels);
		pSpeedCels = DiabloAllocPtr(0x100000);
		LoadSetMap();
		IncProgress();
		GetLevelMTypes();
		InitMonsters();
		InitMissileGFX();
		InitDead();
		FillSolidBlockTbls();
		IncProgress();

		if (lvldir == 5)
			GetPortalLvlPos();

		for (i = 0; i < MAX_PLRS; i++) {
			if (plr[i].plractive && currlevel == plr[i].plrlevel) {
				InitPlayerGFX(i);
				if (lvldir != 4)
					InitPlayer(i, firstflag);
			}
		}

		InitMultiView();
		IncProgress();

		if (firstflag || lvldir == 4 || !plr[myplr]._pSLvlVisited[setlvlnum]) {
			InitItems();
			SavePreLighting();
		} else {
			LoadLevel();
		}

		InitMissiles();
		IncProgress();
	}

	SyncPortals();

	for (i = 0; i < MAX_PLRS; i++) {
		if (plr[i].plractive && plr[i].plrlevel == currlevel && (!plr[i]._pLvlChanging || i == myplr)) {
			if (plr[i]._pHitPoints > 0) {
				if (gbMaxPlayers == 1)
					dPlayer[plr[i].WorldX][plr[i].WorldY] = i + 1;
				else
					SyncInitPlrPos(i);
			} else {
				dFlags[plr[i].WorldX][plr[i].WorldY] |= BFLAG_DEAD_PLAYER;
			}
		}
	}

	if (leveltype != DTYPE_TOWN)
		SetDungeonMicros();

	InitLightMax();
	IncProgress();
	IncProgress();

	if (firstflag) {
		InitControlPan();
		IncProgress();
	}
	if (leveltype != DTYPE_TOWN) {
		ProcessLightList();
		ProcessVisionList();
	}

	music_start(leveltype);

	while (!IncProgress())
		;

	if (setlevel && setlvlnum == SL_SKELKING && quests[QTYPE_KING]._qactive == 2)
		PlaySFX(USFX_SKING1);
}
// 525738: using guessed type int setseed;
// 5CF31D: using guessed type char setlevel;
// 679660: using guessed type char gbMaxPlayers;

void game_loop(BOOL bStartup)
{
	int i;

	i = bStartup ? 60 : 3;

	while (i--) {
		if (!multi_handle_delta()) {
			timeout_cursor(TRUE);
			break;
		} else {
			timeout_cursor(FALSE);
			game_logic();
		}
		if (!gbRunGame || gbMaxPlayers == 1 || !nthread_has_500ms_passed(TRUE))
			break;
	}
}
// 679660: using guessed type char gbMaxPlayers;

void game_logic()
{
	if(PauseMode == 2) {
		return;
	}
	if(PauseMode == 1) {
		PauseMode = 2;
	}
	if(gbMaxPlayers == 1 && gmenu_exception()) {
		drawpanflag |= 1;
		return;
	}

	if(!gmenu_exception() && sgnTimeoutCurs == 0) {
		CheckCursMove();
		track_process();
	}
	if(gbProcessPlayers) {
		ProcessPlayers();
	}
	if(leveltype != DTYPE_TOWN) {
		ProcessMonsters();
		ProcessObjects();
		ProcessMissiles();
		ProcessItems();
		ProcessLightList();
		ProcessVisionList();
	} else {
		ProcessTowners();
		ProcessItems();
		ProcessMissiles();
	}

#ifdef _DEBUG
	if(debug_mode_key_inverted_v && GetAsyncKeyState(VK_SHIFT) & 0x8000) {
		ScrollView();
	}
#endif

	sound_update();
	ClearPlrMsg();
	CheckTriggers();
	CheckQuests();
	drawpanflag |= 1;
	pfile_update(FALSE);
	
	// JAKE: PLRCTRLS
	// check for monsters first, then towners or objs.
	if (pcurs <= 0) { // cursor should be missing
		if (!checkMonstersNearby(false)) {
			pcursmonst = -1;
			checkTownersNearby(false);
			checkItemsNearby(false);
		} else {
			pcursitem = -1;
		}
	}
	charMovement();
}
// 525718: using guessed type char cineflag;
// 52571C: using guessed type int drawpanflag;
// 525740: using guessed type int PauseMode;
// 679660: using guessed type char gbMaxPlayers;

void timeout_cursor(BOOL bTimeout)
{
	if (bTimeout) {
		if (sgnTimeoutCurs == CURSOR_NONE && !sgbMouseDown) {
			sgnTimeoutCurs = pcurs;
			multi_net_ping();
			ClearPanel();
			AddPanelString("-- Network timeout --", TRUE);
			AddPanelString("-- Waiting for players --", TRUE);
			SetCursor_(CURSOR_HOURGLASS);
			drawpanflag = 255;
		}
		scrollrt_draw_game_screen(1);
	} else if (sgnTimeoutCurs) {
		SetCursor_(sgnTimeoutCurs);
		sgnTimeoutCurs = 0;
		ClearPanel();
		drawpanflag = 255;
	}
}
// 52571C: using guessed type int drawpanflag;
// 525748: using guessed type char sgbMouseDown;

void diablo_color_cyc_logic()
{
	DWORD tc;

	tc = GetTickCount();
	if (tc - color_cycle_timer >= 0x32) {
		color_cycle_timer = tc;
		if (palette_get_colour_cycling()) {
			if (leveltype == DTYPE_HELL) {
				lighting_color_cycling();
			} else if (leveltype == DTYPE_CAVES) {
				//if (fullscreen)
					palette_update_caves();
			}
		}
	}
}
// 52574C: using guessed type int color_cycle_timer;

DEVILUTION_END_NAMESPACE
